package com.imc.core.config.websecurity;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.imc.core.aop.ExceptionAop;
import com.imc.core.config.constants.AuthHttpCode;
import com.imc.core.config.mvc.MvcConfigurerAdapter;
import com.imc.core.utils.DateUtils;
import com.imc.platform.base.menu.dao.mapper.IMenuMapper;
import com.imc.platform.base.org.service.IOrgService;
import com.imc.platform.base.role.service.IRoleService;
import com.imc.platform.base.user.dao.mapper.IUserMapper;
import com.imc.platform.base.user.dao.repository.UserRepository;
import com.imc.platform.base.menu.pojo.Menu;
import com.imc.platform.base.user.pojo.User;
import com.imc.platform.base.user.myexception.ValidateCodeException;
import com.imc.platform.base.user.security.CustomUserService;
import com.imc.platform.base.user.security.MyFilterSecurityInterceptor;
import com.imc.platform.base.user.security.ValidateCodeFilter;
import com.imc.platform.base.user.service.IUserService;
import com.imc.platform.frame.pojo.VisitSummary;
import com.imc.platform.frame.repository.VisitSummaryRepository;
import com.imc.platform.frame.service.CountService;
import com.twp.common.tuple.ResultDTO;
import com.twp.common.utils.MD5Util;
import lombok.experimental.Delegate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.*;

/**
 * Created by tanweiping on 2018/7/20.
 */
/*
* 类描述：websecurity权限相关
* 开发者：tanweiping
* 创建日期：2018/8/7 10:26
*/
@ConditionalOnExpression("${security.use:true}")
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebsecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    ValidateCodeFilter validateCodeFilter;

    @Autowired
    MvcConfigurerAdapter mvcConfigurerAdapter;

    @Autowired
    WebSecurityProperty webSecurityProperty;

    @Delegate
    @Autowired
    AuthHttpCode authHttpCode;

    @Autowired
    private MyFilterSecurityInterceptor myFilterSecurityInterceptor;

    @Autowired
    private IUserMapper userMapper;

    @Autowired
    private IMenuMapper menuMapper;

    @Autowired
    private IOrgService orgService;

    @Autowired
    private IRoleService roleService;

    @Autowired
    CountService countService;


    @Autowired
    VisitSummaryRepository visitSummaryRepository;

    /*
    * tanweiping  2018/8/7 10:27
    * 方法描述：注册UserDetailsService 的bean
    */
    @Bean
    UserDetailsService customUserService(){
        return new CustomUserService();
    }

    /*
    * tanweiping  2018/8/7 10:27
    * 方法描述：此处为密码使用md5 进行加密
    */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new PasswordEncoder() {

            @Override
            public String encode(CharSequence rawPassword) {
                return MD5Util.encode(webSecurityProperty.getSalt(),(String) rawPassword);
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return encodedPassword.equals(encode((String) rawPassword));
            }
        };
    }

    /*
    * tanweiping  2018/8/7 10:27
    * 方法描述：user Details Service验证
    */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customUserService()).passwordEncoder(passwordEncoder());
        //user Details Service验证
    }

    /*
    * tanweiping  2018/8/7 10:28
    * 方法描述：配置http权限相关的处理器
    */
    @Override
    @SuppressWarnings("unused")
    protected void configure(HttpSecurity http) throws Exception {
        validateCodeFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
        http.csrf().disable()
                .addFilterBefore(validateCodeFilter,UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers(webSecurityProperty.getPermitAll()).permitAll()
                .anyRequest().authenticated() //任何请求,登录后可以访问
                .and()
                .formLogin()
                .loginProcessingUrl(webSecurityProperty.getLoginProcessingUrl())
                .successHandler(
                        (
                                HttpServletRequest request,
                                HttpServletResponse response,
                                Authentication o
                        )->{
                            countService.UpdateAccessCount();
                            User user = userMapper.queryRoleUserByLoginName(o.getName());

                            HttpSession session = request.getSession();

                            session.setAttribute("currentUser",user);

                            Object onlineCount = session.getServletContext().getAttribute("onlineCount");
                            if (onlineCount==null){
                                onlineCount = 0;
                            }
                            int onlineCount_ = Integer.valueOf(String.valueOf(onlineCount)).intValue() + 1;
                            session.getServletContext().setAttribute("onlineCount", onlineCount_);

                            user.setLoginPwd("******");
                            List<Menu>menus=menuMapper.queryMenuByUserId(String.valueOf(user.getId()));
                            user.setMenuList(menus);

                            user.setOrgId(orgService.queryOrgIdByUserId(String.valueOf(user.getId())));


                            VisitSummary visitSummary = new VisitSummary();
                            visitSummary.setCreateTime(DateUtils.getCurrentDate());
                            visitSummary.setUserId(String.valueOf(user.getId()));
                            visitSummaryRepository.save(visitSummary);

                            processHeaderWrite(
                                    ()->ResultDTO
                                            .builder()
                                            .message(authHttpCode.getSUCCESS().getMessage())
                                            .code(authHttpCode.getSUCCESS().getCode())
                                            .data(
                                                    new HashMap<String,Object>(){
                                                        {
                                                            put("user",user);
                                                        }
                                                    }
                                            )
                                            .build()
                                    ,response);
                        }
                )
                .failureHandler(
                        authenticationFailureHandler
                )
//                .authenticationDetailsSource(
//                        request->{
//                            return new Object();
//                    })
                .permitAll() //登录页面用户任意访问
                .and()
                .logout()
                .logoutUrl(webSecurityProperty.getLogoutUrl())
                .logoutSuccessHandler(
                        (HttpServletRequest request, HttpServletResponse response, Authentication authentication)->{
                            processHeaderWrite(
                                    ()->ResultDTO
                                            .builder()
                                            .message(authHttpCode.getLOGOUT().getMessage())
                                            .code(authHttpCode.getLOGOUT().getCode())
                                            .build()
                                    ,response);
                        }
                )
                .invalidateHttpSession(true)
                .permitAll()
                .and()
                .exceptionHandling()
                .accessDeniedHandler(
                        (HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)->{
                            if(ExceptionAop.isAjaxRequest(request)) {
                                processHeaderWrite(
                                        () -> ResultDTO
                                                .builder()
                                                .isSuccess(false)
                                                .message(authHttpCode.getFORBIDDEN().getMessage())
                                                .code(authHttpCode.getFORBIDDEN().getCode())
                                                .build()
                                        , response);
                            }else {
                                request.setAttribute("errorCode",authHttpCode.getFORBIDDEN().getCode());
                                request.setAttribute("errorMessage",authHttpCode.getFORBIDDEN().getMessage());
                                request.getRequestDispatcher("/login/error").forward(request,response);
                            }
                        }
                )
                .authenticationEntryPoint(
                        (HttpServletRequest request, HttpServletResponse response, AuthenticationException e)->{
                            if(ExceptionAop.isAjaxRequest(request)){
                                processHeaderWrite(
                                        ()->ResultDTO
                                                .builder()
                                                .isSuccess(false)
                                                .message(authHttpCode.getUNAUTHORIZED().getMessage())
                                                .code(authHttpCode.getUNAUTHORIZED().getCode())
                                                .build()
                                        ,response);
                            }else {
                                response.sendRedirect(mvcConfigurerAdapter.getLoginUrl());
                            }

                        })
        .and()
        //最大支持1个session，支持踢人动作
        .sessionManagement().maximumSessions(1)
        //basic base64加密使用
        //.and().httpBasic()
        //.and()
        //.httpBasic()
        ; //注销行为任意访问

        http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
    }

    private void processHeaderWrite(
            IWriteResponseAction iWriteResponseAction,
            HttpServletResponse response
            ) throws IOException {
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        //response.getWriter().print("{\"code\":1,\"message\":\""+e.getMessage()+"\"}");
        Optional.ofNullable(iWriteResponseAction)
                .ifPresent(i -> {
                    try {
                        response.getWriter().print(JSON.toJSONString(i.run(), SerializerFeature.WriteMapNullValue));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
        response.getWriter().flush();
    }

    @FunctionalInterface
    interface IWriteResponseAction{
        ResultDTO run();
    }

    /*
    * tanweiping  2018/8/7 10:28
    * 描述：AuthenticationFailureHandler实现类
    */
    private AuthenticationFailureHandler authenticationFailureHandler =
                            (
                                                        HttpServletRequest request,
                                                        HttpServletResponse response,
                                                        AuthenticationException e)->{
                                                    /*
                                                    UsernameNotFoundException 用户找不到
                                                    BadCredentialsException 坏的凭据
                                                    AccountStatusException 用户状态异常它包含如下子类
                                                    AccountExpiredException 账户过期
                                                    LockedException 账户锁定
                                                    DisabledException 账户不可用
                                                    CredentialsExpiredException 证书过期
                                                     */
                                    ResultDTO resultDTO = ResultDTO.builder().isSuccess(false).build();
                                                    if(e instanceof BadCredentialsException){
                                resultDTO.setCode(authHttpCode.getERRORPWD().getCode());
                                resultDTO.setMessage(authHttpCode.getERRORPWD().getMessage());
                            }else if (e instanceof UsernameNotFoundException){
                                resultDTO.setMessage(authHttpCode.getNONEUSER().getMessage());
                                resultDTO.setCode(authHttpCode.getNONEUSER().getCode());
                            }else if(e instanceof ValidateCodeException){
                                resultDTO.setMessage(authHttpCode.getERRORVERIFY().getMessage());
                                resultDTO.setCode(authHttpCode.getERRORVERIFY().getCode());
                            }
                            processHeaderWrite(()->resultDTO,response);
                        };

}